package com.azzcs;

import com.azzcs.catalina.Container;
import com.azzcs.catalina.context.StandardContext;
import com.azzcs.catalina.engine.StandardEngine;
import com.azzcs.catalina.host.StandardHost;
import com.azzcs.catalina.mapper.Mapper;
import com.azzcs.catalina.wrapper.StandardWrapper;
import com.azzcs.classload.WebAppClassLoad;
import com.azzcs.core.StandardThreadExecutor;
import com.azzcs.core.ThreadExecutor;
import com.azzcs.coyote.connector.Connector;
import com.azzcs.server.Server;
import com.azzcs.server.StandardServer;
import com.azzcs.service.StandardService;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author: wzg
 * @Date: 2021/1/7 下午2:51
 */
public class Catalina {
    private boolean loaded = false;

    protected Server server = null;

    private Mapper mapper = new Mapper();

    public void load() throws Exception {
        if (loaded) {
            return;
        }
        loaded = true;
        loadServerXML();
        server.init();
    }

    private void loadServerXML() throws Exception {
        InputStream input = Catalina.class.getClassLoader().getResourceAsStream("server.xml");
        Document document = new SAXReader().read(input);
        Element rootElement = document.getRootElement();
        server = new StandardServer();
        server.setCatalina(this);
        parseServices(rootElement);

    }

    private void parseServices(Element rootElement) {
        List<Element> services = rootElement.selectNodes("Service");
        for (Element service : services) {
            parseService(service);
        }
    }

    private void parseService(Element element) {
        StandardService service = new StandardService();
        parseServiceName(service, element);
        parseServiceExecutors(service, element);
        parseServiceConnectors(service, element);
        parseEngine(service, element);

        this.server.addService(service);
    }

    private void parseEngine(StandardService service, Element element) {
        Element engine = (Element) element.selectSingleNode("Engine");
        String name = engine.attributeValue("name");
        StandardEngine standardEngine = new StandardEngine();
        standardEngine.setService(service);
        standardEngine.setName(name);
        service.setEngine(standardEngine);

        parseHosts(standardEngine, engine);
    }

    private void parseHosts(StandardEngine standardEngine, Element element) {
        List<Element> hosts = element.selectNodes("Host");
        for (Element host : hosts) {
            parseHost(standardEngine, host);
        }
    }

    private void parseHost(StandardEngine standardEngine, Element host) {
        String name = host.attributeValue("name");
        String appBase = host.attributeValue("appBase");
        StandardHost standardHost = new StandardHost();
        standardHost.setName(name);
        standardEngine.addChild(standardHost);
        standardHost.setParent(standardEngine);
        mapperAddHost(standardHost);

        parseContexts(standardHost, appBase);
    }

    private void mapperAddHost(StandardHost standardHost) {
        mapper.addHost(standardHost.getName(), standardHost);
    }

    private void parseContexts(StandardHost standardHost, String appBase) {
        URL resource = this.getClass().getClassLoader().getResource("");
        System.out.println(resource.getPath());
        String filePath = this.getClass().getClassLoader().getResource(appBase).getFile();
        File webApps = new File(filePath);
        File[] files = webApps.listFiles();
        for (File file : files) {
            parseContext(standardHost, file);
        }
    }

    private void parseContext(StandardHost standardHost, File file) {
        String name = file.getName();
        StandardContext standardContext = new StandardContext();
        standardContext.setName(name);
        standardHost.addChild(standardContext);
        standardContext.setParent(standardHost);
        WebAppClassLoad classLoad = new WebAppClassLoad(file + "/server/");
        standardContext.setClassLoader(classLoad);
        mapperAddContext(standardContext);
        parseWrappers(standardContext, file);
    }

    private void mapperAddContext(StandardContext standardHost) {
        Container parent = standardHost.getParent();
        parent.getName();
        Mapper.MappedHost host = mapper.getHost(parent.getName());
        mapper.addContext(host, standardHost.getName(), standardHost);
    }

    private void parseWrappers(StandardContext standardContext, File context) {
        File[] files = context.listFiles();
        for (File file : files) {
            if ("web.xml".equalsIgnoreCase(file.getName())) {
                parseWrapper(standardContext, file);
            }
        }

    }

    private void parseWrapper(StandardContext standardContext, File webXml) {
        ClassLoader classLoader = standardContext.getClassLoader();
        try {
            InputStream inputStream = new FileInputStream(webXml);
            Document document = new SAXReader().read(inputStream);
            Element rootElement = document.getRootElement();
            List<Element> servlets = rootElement.selectNodes("servlet");
            for (Element servletElement : servlets) {
                StandardWrapper standardWrapper = new StandardWrapper();
                Element servletName = (Element) servletElement.selectSingleNode("servlet-name");
                standardWrapper.setName(servletName.getText());
                Element servletClass = (Element) servletElement.selectSingleNode("servlet-class");
                String servletClassPath = servletClass.getText();
                Class<?> bClass = classLoader.loadClass(servletClassPath);
                Servlet servlet = (Servlet) bClass.newInstance();
                standardWrapper.setServlet(servlet);
                standardContext.addChild(standardWrapper);
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName.getText() + "']");
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                standardWrapper.setUrlPattern(urlPattern);
                standardWrapper.setParent(standardContext);
                mapperAddWrapper(standardWrapper);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void mapperAddWrapper(StandardWrapper standardWrapper) {
        // context
        Container parent = standardWrapper.getParent();
        String contextName = parent.getName();
        String hostName = parent.getParent().getName();
        Mapper.MappedHost host = mapper.getHost(hostName);
        Mapper.MappedContext context = mapper.getContext(host, contextName);
        mapper.addWrapper(context,standardWrapper.getName(),standardWrapper);
    }

    private void parseServiceConnectors(StandardService service, Element serviceElement) {
        List<Element> connector = serviceElement.selectNodes("Connector");
        for (Element element : connector) {
            parseServiceConnector(service, element);
        }
    }

    private void parseServiceConnector(StandardService service, Element element) {
        String port = element.attributeValue("port");
        String executor = element.attributeValue("executor");
        Connector connector = new Connector(Integer.parseInt(port), service, executor,this);
        service.addConnector(connector);
    }

    private void parseServiceExecutors(StandardService service, Element serviceElement) {
        List<Element> executor = serviceElement.selectNodes("Executor");
        for (Element element : executor) {
            parseServiceExecutor(service, element);
        }
    }

    private void parseServiceExecutor(StandardService service, Element element) {
        int maximumPoolSize = 100;
        int corePoolSize = 50;
        String name = element.attributeValue("name");
        String maxThreads = element.attributeValue("maxThreads");
        String minSpareThreads = element.attributeValue("minSpareThreads");
        if (maxThreads != null && !"".equalsIgnoreCase(maxThreads = maxThreads.trim())) {
            maximumPoolSize = Integer.parseInt(maxThreads);
        }
        if (minSpareThreads != null && !"".equalsIgnoreCase(minSpareThreads = minSpareThreads.trim())) {
            corePoolSize = Integer.parseInt(minSpareThreads);
        }
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                60L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue());
        ThreadExecutor threadExecutor = new StandardThreadExecutor(executor, name);
        service.addThreadExecutor(threadExecutor);
    }

    private void parseServiceName(StandardService service, Element serviceElement) {
        String name = serviceElement.attributeValue("name");
        service.setName(name);
    }

    public void start() {
        server.start();
    }

    public Mapper getMapper() {
        return mapper;
    }
}
